using System;
using System.Collections.Generic;
using System.Threading;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Storage;
using Microsoft.Xna.Framework.Content;
using Tomahawk.Runtime.Input;
using Tomahawk.Runtime.Scene;
using Tomahawk.Runtime.Rendering;
using Tomahawk.Runtime.Logic;
using Tomahawk.Runtime.Canvas;
using Tomahawk.Runtime.Audio;
using Tomahawk.Runtime.Debug;
using Tomahawk.Runtime.Rendering.Objects;
using Tomahawk.Runtime.Threading;
using Tomahawk.Runtime.Rendering.Materials;
using Tomahawk.Runtime.Physics;
using Tomahawk.Runtime.Physics.Interfaces;
using Tomahawk.Runtime.Resources;

namespace Tomahawk.Runtime
{
    /// <summary>
    /// This heart of the engine. It's implemented as an XNA Game class, 
    /// so it will receive initialization and update events. This class is 
    /// a singleton, so you can easily access any member from within your game.
    /// </summary>
    public class Engine : Game
    {

        #region "Engine Events"

        public delegate void EngineNotification();
        public event EngineNotification OnBeforeInitialize;
        public event EngineNotification OnAfterInitialize;

        public delegate void UpdateNotification(float elapsedTime);
        public event UpdateNotification OnBeforeUpdate;
        public event UpdateNotification OnAfterUpdate;

        #endregion

        #region "Singleton"

        /// <summary>
        /// Instance of the engine
        /// </summary>
        private static Engine instance = null;

        /// <summary>
        /// Use this static property to access the engine object
        /// </summary>
        public static Engine Instance
        {
            get { return instance; }
            set { instance = value; }
        }

        /// <summary>
        /// Static constructor. Create the singleton instance
        /// </summary>
        static Engine()
        {
            //create the singleton instance
            Engine.instance = new Engine();
        }

        #endregion

        #region "Configuration Propierties"

        private Type engineCofigType = typeof(XEngineConfiguration);

        private String engineConfigArchetype = @"Tomahawk.Content\Config\EngineConfig.xml";

        /// <summary>
        /// Sets the type of the Engine configuration object. 
        /// It allows you use a derived type to extend the config file itself.
        /// </summary>
        public Type EngineConfigType
        {
            get { return this.engineCofigType; }
            set { this.engineCofigType = value; }
        }

        /// <summary>
        /// Sets the engine configuration XML archetype. 
        /// It allows you tu use a custom config file for your game.
        /// </summary>
        public String EngineConfigArchetype
        {
            get { return this.engineConfigArchetype; }
            set { this.engineConfigArchetype = value; }
        }

        #endregion

        #region "Engine Objects"

        //xna objects
        GraphicsDeviceManager graphicsDeviceManager = null;

        //engine objects
        ResourceManager resourceManager = null;
        InputManager inputManager = null;
        SceneManager sceneManager = null;
        CameraManager cameraManager = null;
        CanvasManager canvasManager = null;
        PhysicsManager physicsManager = null;
        AudioManager audioManager = null;
        Debug.Console debugConsole = null;
        SASScene sceneShader = null;
        SASEffectEnvironment effectEnvironment = null;

		XEngineConfiguration engineConfiguration = null;

        //game states
        internal Stack<GameState> gameStates = new Stack<GameState>();
        public GameState CurrentGameState { get { return gameStates.Peek(); } }

        #endregion

        #region "Initialization"

        /// <summary>
        /// Engine constructor. 
        /// Instantiate all the necesary objects.
        /// </summary>
        private Engine()
        {
            //create the core XNA objects
            this.graphicsDeviceManager = new GraphicsDeviceManager(this);

            //create and register the physics service
#if WITH_JIGLIBX
            Physics.JigLibX.PhysicsService physService = new Physics.JigLibX.PhysicsService(this);
            Services.AddService(typeof(IPhysicsService), physService);
#endif

            //set the resolution on the device
#if XBOX
            this.InitGraphicsMode(this.graphicsDeviceManager, 1280, 720, true);
#else
            this.InitGraphicsMode(this.graphicsDeviceManager, 1280, 720, false);
#endif

            //enable antialiasing (doesn't work?)
            //this.graphicsDeviceManager.PreferMultiSampling = true;

            //add the fps counter
            //Components.Add(new Debug.FrameRateCounter(this));

        }

        /// <summary>
        /// Engine Initialization
        /// </summary>
        protected override void Initialize()
        {
            //debug console, to be ready before anything else
            debugConsole = new Tomahawk.Runtime.Debug.Console();

            //create the core Engine subsystem managers
            this.sceneManager = new SceneManager();
            this.physicsManager = new PhysicsManager();
            this.canvasManager = new CanvasManager(this.GraphicsDevice);
            this.inputManager = new InputManager();
            this.cameraManager = new CameraManager();
            this.audioManager = new AudioManager();
            this.resourceManager = new ResourceManager(this.GraphicsDevice);
            this.effectEnvironment = new SASEffectEnvironment();

            //create the default camera
            Camera defCamera =
                Engine.Instance.SceneManager.CreateCamera(
                    this.graphicsDeviceManager.GraphicsDevice.Viewport, "default");

            //Initialize shadow map
            this.SetupShadowMap(this.graphicsDeviceManager.GraphicsDevice);

            //inform the application
            if (this.OnBeforeInitialize != null) OnBeforeInitialize();

            //load the engine and game configuration
            this.engineConfiguration =
                XWorld.Instance.CreateObject(this.engineCofigType, "Configuration", this.engineConfigArchetype)
                as XEngineConfiguration;

            //initialize base Game class (the xna stuff)
            base.Initialize();

            //set a default ambient color
            this.AmbientColor = new Color(1.0f, 1.0f, 1.0f, 1.0f);

            //inform the application
            if (this.OnAfterInitialize != null) OnAfterInitialize();

        }

        /// <summary>
        /// Attempt to set the display mode to the desired resolution.  Itterates through the display
        /// capabilities of the default graphics adapter to determine if the graphics adapter supports the
        /// requested resolution.  If so, the resolution is set and the function returns true.  If not,
        /// no change is made and the function returns false.
        /// </summary>
        /// <param name="iWidth">Desired screen width.</param>
        /// <param name="iHeight">Desired screen height.</param>
        /// <param name="bFullScreen">True if you wish to go to Full Screen, false for Windowed Mode.</param>
        private bool InitGraphicsMode(GraphicsDeviceManager graphics, int iWidth, int iHeight, bool bFullScreen)
        {
            // If we aren't using a full screen mode, the height and width of the window can
            // be set to anything equal to or smaller than the actual screen size.
            if (bFullScreen == false)
            {
                if ((iWidth <= GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Width)
                    && (iHeight <= GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Height))
                {
                    graphics.PreferredBackBufferWidth = iWidth;
                    graphics.PreferredBackBufferHeight = iHeight;
                    graphics.IsFullScreen = bFullScreen;
                    graphics.ApplyChanges();
                    return true;
                }
            }
            else
            {
                // If we are using full screen mode, we should check to make sure that the display
                // adapter can handle the video mode we are trying to set.  To do this, we will
                // iterate thorugh the display modes supported by the adapter and check them against
                // the mode we want to set.
                foreach (DisplayMode dm in GraphicsAdapter.DefaultAdapter.SupportedDisplayModes)
                {
                    // Check the width and height of each mode against the passed values
                    if ((dm.Width == iWidth) && (dm.Height == iHeight))
                    {
                        // The mode is supported, so set the buffer formats, apply changes and return
                        graphics.PreferredBackBufferWidth = iWidth;
                        graphics.PreferredBackBufferHeight = iHeight;
                        graphics.IsFullScreen = bFullScreen;
                        graphics.ApplyChanges();
                        return true;
                    }
                }
            }
            return false;
        }

        #endregion

        #region "Loading"

        protected override void LoadContent()
        {
            base.LoadContent();

            //load engine asset for low level usage
            this.debugConsole.LoadGraphicsContent(this.graphicsDeviceManager.GraphicsDevice, this.Content);

            //load the scene shader used to render the game scene
            SceneShader = SASMaterialCollection.Load<SASScene>(engineConfiguration.sceneShaderName, this.Content);
        }

        #endregion

        #region "Rendering"

        /// <summary>
        /// Rendering pseudo stages on which renderable components can render itself
        /// </summary>
        public enum RenderingStages
        {
            Background,
            BeforeScene,
            Scene,
            AfterScene,
            Hud
        }

        private Color clearColor = Color.CornflowerBlue;

        public Color ClearColor
        {
            get { return clearColor; }
            set { clearColor = value; }
        }

        private float clearDepth = 1.0f;

        public float ClearDepth
        {
            get { return clearDepth; }
            set { clearDepth = value; }
        }

        private Color ambientColor = new Color(1.0f, 1.0f, 1.0f, 1.0f);

        /// <summary>
        /// Sets the ambient color
        /// </summary>
        public Color AmbientColor
        {
            get { return ambientColor; }
            set
            {
                ambientColor = value;
                effectEnvironment.light0.ambient = value.ToVector4();
            }
        }

        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice gd = this.graphicsDeviceManager.GraphicsDevice;

            //tick the prerender event on all renderable objects
            XWorld.Instance.PreRender();

            if (sceneShader != null)
            {
                SASMaterial.currentRenderMode = SASMaterial.RenderModes.Color;
                sceneShader.DrawScene(gd);
            }
            else
            {
                this.graphicsDeviceManager.GraphicsDevice.Clear(ClearOptions.Target|ClearOptions.DepthBuffer, clearColor, 1.0f, 0);
                SASMaterial.currentRenderMode = SASMaterial.RenderModes.Color;
                OnDrawGeometry(gd);
            }

            //draw the screen controls
            this.canvasManager.Render();

            //draw the debug console
            this.debugConsole.Render(gd);

            //let the base draw
            base.Draw(gameTime);
        }

        protected bool OnDrawGeometry(GraphicsDevice device)
        {
            SceneManager.Render(device);
            return true;
        }

        #region "Shadow Mapping"

        RenderTarget2D rtShadowMap;
        Texture2D txShadowMap;

        /// <summary>
        /// Allow acess to shadow map texture
        /// </summary>
        public Texture2D ShadowMapTexture
        {
            get { return this.txShadowMap; }
        }

        /// <summary>
        /// Create the shadow map render target
        /// </summary>
        private void SetupShadowMap(GraphicsDevice device)
        {
            rtShadowMap = new RenderTarget2D(device, 512, 512, 1, SurfaceFormat.Color);
        }

        /// <summary>
        /// Render the shadow map
        /// </summary>
        public void RenderShadows(GraphicsDevice device)
        {
            device.RenderState.AlphaBlendEnable = false;
            device.RenderState.AlphaTestEnable = false;
            device.RenderState.DepthBufferEnable = true;
            device.RenderState.DepthBufferFunction = CompareFunction.LessEqual;
            device.RenderState.DepthBufferWriteEnable = true;

            //activate the render target
            device.SetRenderTarget(0, this.rtShadowMap);

            //clear the shadow map in white
            this.graphicsDeviceManager.GraphicsDevice.Clear(Color.White);

            //render in shadow mode
            //this.sceneManager.Render(this.graphicsDeviceManager.GraphicsDevice, true);

            //get the rendered texture
            device.SetRenderTarget(0, null);
            this.txShadowMap = this.rtShadowMap.GetTexture();
        }

        #endregion

        #endregion

        #region "Physics"
        public IPhysicsService PhysService
        {
            get
            {
                return (IPhysicsService)Services.GetService(typeof(IPhysicsService));
            }
        }
        #endregion

        #region "Public Properties (Engine Objects)"

        /// <summary>
        /// Allow access to the GraphicDevice Manager
        /// </summary>
        public GraphicsDeviceManager GraphicDeviceManager
        {
            get { return this.graphicsDeviceManager; }
        }

        /// <summary>
        /// Allow access to the Scene Manager
        /// </summary>
        public SceneManager SceneManager
        {
            get { return this.sceneManager; }
        }

        /// <summary>
        /// Allow access to the Input Manager
        /// </summary>
        public InputManager InputManager
        {
            get { return this.inputManager; }
        }

        /// <summary>
        /// Allow access to the Camera Manager
        /// </summary>
        public CameraManager CameraManager
        {
            get { return this.cameraManager; }
        }

        /// <summary>
        /// Allow access to the Canvas Manager
        /// </summary>
        public CanvasManager CanvasManager
        {
            get { return this.canvasManager; }
        }

        /// <summary>
        /// Allow access to the Physics Manager
        /// </summary>
        public PhysicsManager PhysicsManager
        {
            get { return this.physicsManager; }
        }

        /// <summary>
        /// Allow access to the Audio Manager
        /// </summary>
        public AudioManager AudioManager
        {
            get { return this.audioManager; }
        }

        /// <summary>
        /// Allow acces to the Writer
        /// </summary>
        public Debug.Console DebugConsole
        {
            get { return this.debugConsole; }
        }

        /// <summary>
        /// Resource Manager of the Engine
        /// </summary>
        public ResourceManager ResourceManager
        {
            get { return this.resourceManager; }
        }

        /// <summary>
        /// 
        /// </summary>
        public SASScene SceneShader
        {
            get { return this.sceneShader; }
            set
            {
                if (this.sceneShader != null)
                {
                    this.sceneShader.UnloadSurfaces(this.GraphicsDevice);
                }

                this.sceneShader = value;
                this.sceneShader.LoadSurfaces(this.GraphicsDevice);
                this.sceneShader.OnDrawGeometry += new SASScene.DrawGeometryDelegate(OnDrawGeometry);
            }
        }

        /// <summary>
        /// The environment applied to SAS effects
        /// </summary>
        public SASEffectEnvironment EffectEnvironment
        {
            get { return this.effectEnvironment; }
        }

        /// <summary>
        /// Engine configuration object. 
        /// Note: Upcast to the game engine config type, 
        /// if you are using custom configuration classes
        /// </summary>
        public XEngineConfiguration EngineConfiguration
        {
            get { return this.engineConfiguration; }
        }

        #endregion

        #region "World Update"

        /// <summary>
        /// Time elapse speed control factor
        /// </summary>
        private float timeSpeed = 1.0f;

        /// <summary>
        /// Controls how fast/slow the time will elapse
        /// </summary>
        public float TimeSpeed
        {
            get { return this.timeSpeed; }
            set { this.timeSpeed = value; }
        }

        /// <summary>
        /// Launch the update cycle on all the updateable sub-systems
        /// </summary>
        protected override void Update(GameTime gameTime)
        {
            //get the elapsed time
            float elapsedTime = (float)(gameTime.ElapsedGameTime.TotalSeconds * timeSpeed);

            //synchronize the physics scene and the logic scene from the results of the physics simulation
            this.PhysicsManager.WaitForResults();
            this.PhysicsManager.UpdateWorld();

            if (this.OnBeforeUpdate != null) OnBeforeUpdate(elapsedTime);

            //update the debug console
            this.debugConsole.Update(elapsedTime);

            //update the managers that requires so
            this.inputManager.Update(elapsedTime);

            //Update the logic world
            XWorld.Instance.Update(elapsedTime);

            //Update the scene tree
            this.sceneManager.Update();

            lastElapsedTime = elapsedTime;

            //load the pendant assets
            //TODO: do this asynchronouslly
            this.resourceManager.LoadPendantResources(this.CurrentGameState.ContentManager);

            if (this.OnAfterUpdate != null) OnAfterUpdate(elapsedTime);

            //Advance the physics simulation. The results will be available on the next tick
            this.PhysicsManager.Simulate(elapsedTime);

            //call the base system
            base.Update(gameTime);
        }

        private float lastElapsedTime;

        public float LastElapsedTime
        {
            get { return lastElapsedTime; }
        }

        #endregion

        /// <summary>
        /// Stops the engine, and exits the game
        /// </summary>
        public void Stop()
        {
            //stops the game
            this.Exit();
        }

        /// <summary>
        /// Reset the device state for 3D content
        /// </summary>
        private void setDeviceStateDefault3D(GraphicsDevice device)
        {
            device.RenderState.AlphaBlendEnable = true;
            device.RenderState.SourceBlend = Blend.One;
            device.RenderState.DestinationBlend = Blend.DestinationAlpha;
            device.SamplerStates[0].AddressU = TextureAddressMode.Wrap;
            device.SamplerStates[0].AddressV = TextureAddressMode.Wrap;
        }

        protected override void EndRun()
        {
            physicsManager.Dispose();
            base.EndRun();
        }

    }

}
